package com.avcompris.util;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;
import static com.avcompris.util.XMLUtils.xmlEncode;
import static com.avcompris.util.XMLUtils.xmlEncodeChars;

import java.io.IOException;
import java.io.Writer;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import com.avcompris.common.annotation.Nullable;
import com.avcompris.lang.NotImplementedException;

/**
 * couldn't find a simple XML Serializer that works out of the box,
 * so let's have our own.
 * 
 * @author David Andriana Copyright Avantage Compris SARL 2009 ©
 */
public class AvcXMLSerializer implements ContentHandler {

	/**
	 * constructor.
	 * 
	 * @param writer the writer to use.
	 */
	public AvcXMLSerializer(final Writer writer) {

		this.writer = nonNullArgument(writer, "writer");
	}

	/**
	 * the writer to use.
	 */
	private final Writer writer;

	/**
	 * <tt>true</tt> if we currently are in a startElement() state.
	 */
	private boolean isStartElement = false;

	/**
	 * write the characters.
	 */
	@Override
	public void characters(
			final char[] chars,
			final int start,
			final int length) throws SAXException {

		nonNullArgument(chars, "chars");

		try {

			terminateStartElement();

			writer.write(xmlEncodeChars(chars, start, length));

		} catch (final IOException e) {
			throw new SAXException(e);
		}
	}

	/**
	 * do nothing.
	 */
	@Override
	public void endDocument() throws SAXException {

		// do nothing
	}

	/**
	 * end an element.
	 */
	@Override
	public void endElement(
			@Nullable final String uri,
			final String localName,
			@Nullable final String qName) throws SAXException {

		nonNullArgument(localName, "localName");

		if (uri != null) {
			throw new IllegalArgumentException("uri should be null: " + uri);
		}
		if (qName != null) {
			throw new IllegalArgumentException("qName should be null: " + qName);
		}

		try {

			if (isStartElement) {

				isStartElement = false;

				writer.write('/');
				writer.write('>');

			} else {

				writer.write('<');
				writer.write('/');
				writer.write(localName);
				writer.write('>');

			}

		} catch (final IOException e) {
			throw new SAXException(e);
		}
	}

	/**
	 * throw an exception.
	 */
	@Override
	public void endPrefixMapping(
			@Nullable final String prefix) throws SAXException {

		throw new NotImplementedException();
	}

	/**
	 * throw an exception.
	 */
	@Override
	public void ignorableWhitespace(
			@Nullable final char[] chars,
			final int start,
			final int length) throws SAXException {

		throw new NotImplementedException();
	}

	/**
	 * throw an exception.
	 */
	@Override
	public void processingInstruction(
			@Nullable final String target,
			@Nullable final String data) throws SAXException {

		throw new NotImplementedException();
	}

	/**
	 * throw an exception.
	 */
	@Override
	public void setDocumentLocator(
			@Nullable final Locator locator) {

		throw new NotImplementedException();
	}

	/**
	 * throw an exception.
	 */
	@Override
	public void skippedEntity(
			@Nullable final String name) throws SAXException {

		throw new NotImplementedException();
	}

	/**
	 * do nothing.
	 */
	@Override
	public void startDocument() throws SAXException {

		// do nothing
	}

	/**
	 * start en element.
	 */
	@Override
	public void startElement(
			@Nullable final String uri,
			final String localName,
			@Nullable final String qName,
			@Nullable final Attributes attributes) throws SAXException {

		nonNullArgument(localName, "localName");

		if (uri != null) {
			throw new IllegalArgumentException("uri should be null: " + uri);
		}
		if (qName != null) {
			throw new IllegalArgumentException("qName should be null: " + qName);
		}

		try {

			terminateStartElement();

			writer.write('<');
			writer.write(localName);

			if (attributes != null) {

				for (int i = 0; i < attributes.getLength(); ++i) {

					final String attributeName = attributes.getLocalName(i);
					final String attributeValue = xmlEncode(attributes
							.getValue(i));

					if (attributeName == null) {
						throw new NullPointerException(
								"Attribute's localName is null. QName="
										+ attributes.getQName(i) + ", URI="
										+ attributes.getURI(i));
					}

					writer.write('\r');
					writer.write(attributeName);
					writer.write('=');
					writer.write('"');
					writer.write(attributeValue);
					writer.write('"');
				}
			}

			isStartElement = true;

		} catch (final IOException e) {
			throw new SAXException(e);
		}
	}

	/**
	 * throw an exception.
	 */
	@Override
	public void startPrefixMapping(
			@Nullable final String prefix,
			@Nullable final String uri) throws SAXException {

		throw new NotImplementedException();
	}

	/**
	 * terminate the "startElement()" state if necessary.
	 */
	private void terminateStartElement() throws IOException {

		if (isStartElement) {

			isStartElement = false;

			writer.write('>');
		}
	}
}
